﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

using Microsoft.Scripting;
using MSAst = Microsoft.Scripting.Ast;

namespace DlrBasic.Parser.Ast
{
    using Ast = Microsoft.Linq.Expressions.Expression;

    static class TypeConversionUtils
    {
        internal static Type IntType = typeof(short);
        internal static Type SingleType = typeof(float);
        internal static Type DoubleType = typeof(double);
        internal static Type StringType = typeof(string);

        public static MethodInfo FindExactMethod(Type utilityType, string methodPrefix, Type operandType)
        {
            return FindExactMethod(utilityType, methodPrefix, operandType, false);
        }

        public static MethodInfo FindExactMethod(Type utilityType, string methodPrefix, Type operandType, bool instance)
        {
            string suffix = GetTypeSuffix(operandType);

            string methodName = methodPrefix + suffix;

            BindingFlags flags;
            if (instance)
                flags = BindingFlags.Instance | BindingFlags.Public;
            else
                flags = BindingFlags.Static | BindingFlags.Public;

            MethodInfo info = utilityType.GetMethod(methodName, flags);
            return info;
        }

        public static string GetTypeSuffix(Type type)
        {
            string suffix;
            if (type == IntType)
                suffix = "Int";
            else if (type == StringType)
                suffix = "String";
            else if (type == SingleType)
                suffix = "Single";
            else if (type == DoubleType)
                suffix = "Double";
            else
                throw new DlrBasicException(Properties.Resources.UnsupportedTypeException);
            return suffix;
        }

        public static Type FindMaxType(Type type1, Type type2)
        {
            if (type1 == StringType || type2 == StringType)
                return StringType;
            else if (type1 == DoubleType || type2 == DoubleType)
                return DoubleType;
            else if (type1 == SingleType || type2 == SingleType)
                return SingleType;
            else if (type1 == IntType || type2 == IntType)
                return IntType;
            throw new DlrBasicException(Properties.Resources.UnsupportedTypeException);
        }

        public static bool FindMethod(Type utilityType, string methodPrefix, Type operandType, out MethodInfo info, out Type operandShallBeType)
        {
            operandShallBeType = null;
            info = FindExactMethod(utilityType, methodPrefix, operandType);
            if (info != null)
            {
                operandShallBeType = operandType;
                return true;
            }

            // fallbacks
            if (info == null && operandType == IntType)
            {
                info = FindExactMethod(utilityType, methodPrefix, SingleType);
                if (info != null)
                {
                    operandShallBeType = SingleType;
                    return true;
                }
            }

            if(info == null && 
                (operandType == IntType || operandType == SingleType))
            {
                info = FindExactMethod(utilityType, methodPrefix, DoubleType);
                if (info != null)
                {
                    operandShallBeType = DoubleType;
                    return true;
                }
            }

            if (info == null && operandType == DoubleType)
            {
                info = FindExactMethod(utilityType, methodPrefix, SingleType);
                if (info != null)
                {
                    operandShallBeType = SingleType;
                    return true;
                }
            }

            if(info == null && 
                (operandType == DoubleType || operandType == SingleType))
            {
                info = FindExactMethod(utilityType, methodPrefix, IntType);
                if (info != null)
                {
                    operandShallBeType = IntType;
                    return true;
                }
            }

            return false;
        }

        public static void FindMethodAndConvertOperands(Type utilityType, string methodPrefix, Ast operand, out MethodInfo method, out Ast convertedOperand)
        {
            Type operandType;
            if (!FindMethod(utilityType, methodPrefix, operand.Type, out method, out operandType))
            {
                throw new DlrBasicException(Properties.Resources.MethodNotFoundException);
            }

            convertedOperand = BuildConversionTo(operandType, operand);
        }

        public static void FindMethodAndConvertOperands(Type utilityType, string methodPrefix, Ast operand1, Ast operand2,
            out MethodInfo method, out Ast convertedOperand1, out Ast convertedOperand2)
        {
            Type maxType = FindMaxType(operand1.Type, operand2.Type);
            Type operandType;
            if (!FindMethod(utilityType, methodPrefix, maxType, out method, out operandType))
            {
                throw new DlrBasicException(Properties.Resources.MethodNotFoundException);
            }

            convertedOperand1 = BuildConversionTo(operandType, operand1);
            convertedOperand2 = BuildConversionTo(operandType, operand2);
        }

        public static Ast BuildConversionTo(Type type, Ast expression)
        {
            if (expression.Type == type)
                return expression;

            if (type == StringType || expression.Type == StringType)
                throw new DlrBasicException(Properties.Resources.TypeMismatchException);

            return Ast.ConvertHelper(expression, type);
        }

        public static Ast BuildTestIfTrue(Ast expr)
        {
            return Ast.NotEqual(Ast.Constant((short)0),
                TypeConversionUtils.BuildConversionTo(typeof(short), expr));
        }
    }
}
